use axum::{
    extract::{Request, State},
    http::StatusCode,
    middleware::{self, Next},
    response::{IntoResponse, Json, Response},
    Router,
};
use toml;
mod common;
mod router;
use crate::common::db::DatabaseState;
use crate::common::ApiResult;
use mysql::prelude::*;
use mysql::*;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

const API_PREFIX: &str = "/prod-api/vue-element-admin";
#[derive(Deserialize, Serialize, Debug)]
struct AppConfig {
    db: common::db::DbConfig,
}
async fn check_token(
    State(db_state): State<Arc<Mutex<DatabaseState>>>,
    request: Request,
    //headers: HeaderMap,
    next: Next,
) -> Response {
    let re = Regex::new(format!("^{}(.*)", API_PREFIX).as_str()).unwrap();
    let need_check = match re.captures(request.uri().to_string().as_str()) {
        Some(cap) => {
            let not_need_check = ["/user/login"];
            let uri = cap.get(1).unwrap().as_str();
            println!("{} {}", uri, not_need_check.contains(&uri));
            !not_need_check.contains(&uri)
        }
        _ => true,
    };
    if !need_check {
        next.run(request).await
    } else {
        let headers = request.headers();
        if headers.contains_key("x-token") {
            let x_token = headers["x-token"].to_str().unwrap();
            // println!("\x1b[31marticle/list token is\x1b[0m \"{}\"", x_token);
            let mut conn = db_state.lock().unwrap().pool.get_conn().unwrap();
            let result: Option<(usize, String, String)> = conn
            .exec_first(
                "select `user`.`id`,`user`.`nick`,`user`.`name` from `token` join `user` on `token`.`user_id`=`user`.`id` where `token`=:token and `expire`>unix_timestamp(now())",
                params! { "token" => x_token }
            )
            .unwrap();
            match result {
                Some(_) => next.run(request).await,
                None => Json(ApiResult::<String> {
                    code: common::ERROR_BAD_TOKEN,
                    data: None,
                    message: Some("bad token".to_string()),
                })
                .into_response(),
            }
        } else {
            Json(ApiResult::<String> {
                code: common::ERROR_BAD_TOKEN,
                data: None,
                message: Some("bad token".to_string()),
            })
            .into_response()
        }
    }
}

#[tokio::main]
async fn main() {
    let toml_string = tokio::fs::read_to_string("./config.toml").await.unwrap();
    let config: AppConfig = toml::from_str(toml_string.as_str()).unwrap();
    let db = common::db::init(&config.db);
    let app = Router::new();
    let app = router::route(app, API_PREFIX.to_string()).await;
    let app = app
        .route_layer(middleware::from_fn_with_state(db.clone(), check_token))
        .with_state(db)
        .fallback(handler_404);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
async fn handler_404() -> impl IntoResponse {
    (
        StatusCode::OK,
        Json(ApiResult {
            code: common::ERROR_NOT_FOUND,
            message: None,
            data: Some("api not found".to_string()),
        }),
    )
}
